package com.zentrale1.noodlemail.actions;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Properties;

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Message.RecipientType;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import com.zentrale1.noodlemail.Mail;
import com.zentrale1.noodlemail.MailFactory;
import com.zentrale1.noodlemail.Maildir;
import com.zentrale1.noodlemail.Noodlemail;
/*
 * The MIT License
 * 
 * Copyright (c) 2010 Andreas Fleig
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */

/**
 * Creates replies to messages and stores them in the drafts folder
 * @author afleig
 *
 */
public class ReplyAction extends Action
{
	String[] folders;
	static final String WROTE = "wrote";
	// map of class -> arraylist<file paths of replies to messages of class>
	HashMap<String, ArrayList<String>> classReplies;

	public ReplyAction(Noodlemail noodle) 
	{
		super(noodle);
		
		// get folder, message id and in-reply-to from all messages
		Noodlemail.log("Creating list of replies...");
		HashMap<String, Mail> idMap = new HashMap<String, Mail>();
		addMessagesToMap(noodle.getMaildir(), idMap);
		Maildir[] subfolders = noodle.getMaildir().getFolders();
		for (Maildir dir : subfolders) {
			addMessagesToMap(dir, idMap);
		}
		
		// initialize classReplies
		folders = noodle.listFolders();
		classReplies = new HashMap<String, ArrayList<String>>();
		for (String f : folders) {
			ArrayList<String> replyList = new ArrayList<String>();
			classReplies.put(f, replyList);
		}
		
		int i=0;
		// create lists of replies for each folder
		for (Mail reply : idMap.values()) {
			String rt = reply.getReplyTo();
			if (rt != null && !rt.equals("") && idMap.containsKey(rt)) {
				Mail to = idMap.get(rt);
				String mailClass = to.getFolder();
				ArrayList<String> replyList = classReplies.get(mailClass);
				replyList.add(reply.getPath());
				i++;
			}
		}
		Noodlemail.log(i + " replies found");
	}
	
	
	private void addMessagesToMap(Maildir dir, HashMap<String, Mail> idMap)
	{
		String name = dir.getName();
		if (name == null) {
			Noodlemail.log("Error: no name for "+dir.getPath());
		}
		String[] files = dir.listMessages(false);
		for (String file : files) {
			Mail m = MailFactory.parse(file);
			m.setFolder(name);
			m.setPath(file);
			idMap.put(m.getMessageId(), m);
		}
	}

	
	@Override
	public String getName() 
	{
		return "reply";
	}

	
	@Override
	public void perform(String subPath, String mailClass) 
	{
		ArrayList<String> replyList;
		
		// find reply to use as template
		if (classReplies.containsKey(mailClass)) {
			replyList = classReplies.get(mailClass); 
			
			// magically pick... the first reply
			if (replyList.size() > 0) {
				String path = replyList.get(0);
				Mail oldReply = MailFactory.parse(path);
				Noodlemail.log("  Reply template: \"" + oldReply.getSubject()
						+ "\"");
				
				String filename = noodle.getMaildir().getPath() + "/" + subPath;
				Mail request = MailFactory.parse(filename);
				createDraft(request, oldReply);
				
				return;
			}
		}
		Noodlemail.log("* No reply found for class "+mailClass);
		// rewrite
	}
	
	
	/**
	 * Creates a draft for a reply
	 * @param request
	 * @param oldReply
	 */
	private void createDraft(Mail request, Mail oldReply)
	{
		// create new mail
		Properties p = System.getProperties();
	    Session session = Session.getDefaultInstance(p, null);
	    Message draft = new MimeMessage(session);
	   
	    try {
	    	// from address
		    String fromAddress = noodle.getProperties().getProperty("from.address");
		    String fromName = noodle.getProperties().getProperty("from.name");
			draft.setFrom(new InternetAddress(fromAddress, fromName));
			
			// to address
			String toName = request.getFromName();
			String toAddress = request.getFromAddress();
			InternetAddress to;
			if (toName == null) {
				to = new InternetAddress(toAddress);
			} else {
				to = new InternetAddress(toAddress, toName);
			}
			draft.addRecipient(RecipientType.TO, to);
			
			// subject
			String subject = request.getSubject();
			if (!subject.substring(0, 2).equals("Re:")) {
				subject = "Re: "+subject;
			}
			draft.setSubject(subject);
			
			// in-reply-to
			draft.setHeader("In-Reply-To", request.getMessageId());
			draft.setHeader("References", request.getMessageId());
			
			// date
			draft.setSentDate(new Date());		
			
			// body
			draft.setText(createDraftBody(request, oldReply));
			
			// store
			File draftFile;
			do {
				String filename = getDraftFilename();
				draftFile = new File(filename);
				Thread.sleep(20);
			} while (draftFile.exists());
			
			Noodlemail.log("  Writing Draft to "+draftFile.getAbsolutePath());
			FileOutputStream out = new FileOutputStream(draftFile);
			draft.writeTo(out);
			out.flush();
			out.close();
			
		} catch (UnsupportedEncodingException e) {
			Noodlemail.log("Error: From-Address or Name invalid");
			e.printStackTrace();
		} catch (MessagingException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		

	}
	
	
	/**
	 * Creates a new mail body
	 * @param request
	 * @param oldReply
	 * @return
	 */
	private String createDraftBody(Mail request, Mail oldReply)
	{
		// split text into lines
		String rt = oldReply.getTextBody();
		String[] lines = rt.split("(\r\n)|(\n)");
		
		// find out whether reply or quote comes first
		int[] quotiness = getQuotiness(lines);
		
		String[] text = removeQuotes(lines, quotiness);
		text = swapNames(text, request.getFromName());

		if (quotiness[1] > 0) {
			// quote first, then reply			
			String body = quoteMessage(request) + "\n";
			for (String line : text) {
				body += line + "\n";
			}
			return body;
			
		} else {
			// reply first, then quote
			String body = "";
			for (String line : text) {
				body += line + "\n";
			}
			body += "\n" + quoteMessage(request);
			return body;
	
		}
	}
	
	
	/**
	 * A short-sighted measure of closeness to mail quotes.
	 * For an array of Strings (lines), creates an integer array. For every 
	 * line starting with ">", the corresponding integer is incremented by 2. 
	 * Integers before and after are incremented by 1. 
	 * @param lines
	 * @return
	 */
	private int[] getQuotiness(String[] lines)
	{
		int[] quotiness = new int[lines.length];
		for (int i=0; i<quotiness.length; i++) {
			quotiness[i] = 0;
		}
		
		for (int i=0; i<lines.length; i++) {
			if (lines[i].length() > 0 && lines[i].charAt(0) == '>') {
				if (i-1 >= 0)
					quotiness[i-1]++;
				if (i+1 < lines.length)
					quotiness[i+1]++;
				quotiness[i] += 2;
			}
		}
		
		return quotiness;
	}
	
	
	/**
	 * Removes all quote blocks from a message
	 * @param lines message, split into lines
	 * @param quotiness getQuotiness output
	 * @return message, split into lines, with quote blocks removed
	 */
	private String[] removeQuotes(String[] lines, int[] quotiness)
	{
		ArrayList<String> text = new ArrayList<String>();
		int inBlock = 0;
		for (int i=0; i<lines.length; i++) {
			if (quotiness[i] - inBlock > 0)
				inBlock = 1;
			else {
				inBlock = 0;
				if (text.size() > 0 || !lines[i].matches("^\\s*$"))
					text.add(lines[i]);
			}	
		}
		return text.toArray(new String[text.size()]);
	}
	
	
	/**
	 * Comparator for sorting strings by length, longest first
	 * @author afleig
	 *
	 */
	private class lengthComparator implements Comparator<String>
	{

		@Override
		public int compare(String o1, String o2) 
		{
			return o2.length() - o1.length();
		}
		
	}
	
	
	/**
	 * Swaps the names in the message
	 * @param lines
	 * @param fromName
	 * @return
	 */
	private String[] swapNames(String[] lines, String toName)
	{
		for (int i=0; i<lines.length; i++) {
			if (lines[i].matches("^[^,!.-]+[,!.-]+\\s*$")) {
				// load and sort addresses (as in "hello peter", 
				// not as in "peter@initech.com")
				String addresses = noodle.getProperties()
					.getProperty("addresses");
				
				if (addresses == null) {
					Noodlemail.log("Error: Noodlemail doesn't know how to "
							+ "address people!");
					break;
				}
				
				String[] a = addresses.split(",");
				Arrays.sort(a, new lengthComparator());
				for (int j=0; j<a.length; j++) {
					if (lines[i].toLowerCase().startsWith(a[j].toLowerCase())) {
						lines[i] = a[j] + " " + toName + ",";
						break;
					}
				}
				
				break;
			}
			if (!lines[i].matches("^\\s*$"))
				break;
		}
		
		return lines;
	}
	
	
	/**
	 * Returns a quoted version of the text of a given mail
	 * @param mail a message
	 * @return quoted version of the message
	 */
	private String quoteMessage(Mail mail)
	{
		String from;
		if (mail.getFromName() == null)
			from = mail.getFromAddress();
		else
			from = mail.getFromName();
		
		String msg = from + " "+ WROTE + ":\n";
		String[] lines = mail.getTextBody().split("(\r\n)|(\n)");
		for (String line : lines) {
			msg += "> " + line + "\n";
		}
		
		return msg;
	}
	
	
	private String getDraftFilename()
	{
		long unix = System.currentTimeMillis() / 1000;
		long micro = System.nanoTime() / 1000;
		if (micro < 0)
			micro = micro * -1;
		String file = unix + "." + "V" + micro + "M31337." 
			+ noodle.getHostname() + ":2,";
		String folder = noodle.getMaildir().getPath() + "/" 
			+ noodle.getProperties().getProperty("drafts");
		return folder + "/cur/" + file;
	}
	
}
